Source file: /~heha/mb-iwp/Phasenfinder/Phasenfinder II.zip/pf2.a90

/* Programm für Drehstrom-Phasenfinder II
 * für ATtiny13 (auch ATtiny11, ATtiny12? Fehlt PWM beim Timer)
 * Hardware:
 * Das Gerät hat eine „Antenne“ zum kapazitiven Abgriff der Spannung
 * (das Erdpotenzial bildet der Bediener); es misst also das elektrische Feld.
 * Damit wird das Mikrocontrollerprogramm aufgeweckt, das dann anschließend
 * die Periodendauer und danach die Phasenlage misst.
 * Drei LEDs zeigen die Phasenlage in je 64 Zwischenstufen an.
 * „h#s“ Henrik Haftmann, TU Chemnitz, 12. Februar 2011
Anzeigen:
Alle LEDs aus		Standby-Betrieb
Alle LEDs leuchten	Frequenz-Suche
1-2 LEDs leuchten	Phasen-Verfolgung und -Anzeige
1-2 LEDs blinken	kein Eingangssignal, letzte Phase wird angezeigt
Alle LEDs blinken	kein Eingangssignal, dann Übergang zum Standby
 *
 * tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
 */
#define DATEYEAR	2013
#define DATEMONTH	2
#define DATEDAY		1

#define DEBUG

/************
 * Hardware *
 ************/
/*
Verwendung der Ports:
PB0	(5)	-	LED L1 nach Masse
PB1	(6)	-	LED L2 nach Masse
PB2	(7)	-	LED L3 nach Masse
PB3	(2)	-	Gemeinsame LED und Piepser — oder Debug-Ausgang
PB4	(3)	-	Antenne mit externem Pull-Down
PB5	(1)	RESET	frei
*/

#define L1 0		// Bitnummern für LEDs
#define L2 1
#define L3 2
#define AN 4		// Bitnummer für Antenne
#define DO 3		// Bitnummer Piep-Ausgang

#define LDN 5		// Anzahl Perioden zur Frequenzbestimmung (4 => 16)

#define TO 7		// Zeit der Synchronhaltung, in s (TimeOut)
			// 0 = Maximum, max. 127
#define TAPO 5		// Zeit bis zum Ausschalten, in s (Auto Power Off)
			// 0 = 128, max. 127

// Erwartete Timerticks zwischen zwei fallenden Pegeln
#define F_TMR F_CPU/8		// 150 kHz
// (Ungenauigkeit des Oszillators einkalkulierend)
#define TMAX F_TMR/45		// 3809, entsprechend 45 Hz
#define TMIN F_TMR/65		// 2307, entsprechend 65 Hz
#define TDEV 40			//       entsprechend ± 0,? Hz
#define TTOT F_TMR/25		// 6000, nur High-Teil = 23 wird benutzt
#define TGLI F_TMR/1000		//  150, alles unter 1 ms ist ein Glitch

/**********
 * Makros *
 **********/
.nolist
.include "tn13def.inc"

.macro addHL	;reg,reg
	add	@0L,@1L
	adc	@0H,@1H
.endm
.macro subHL	;reg,reg
	sub	@0L,@1L
	sbc	@0H,@1H
.endm
.macro cmpHL	;reg,reg
	cp	@0L,@1L
	cpc	@0H,@1H
.endm
.macro movHL	;reg,reg
	movw	@0H:@0L,@1H:@1L
.endm
.macro lsrHL	;reg
	lsr	@0H
	ror	@0L
.endm
.macro rorHL	;reg
	ror	@0H
	ror	@0L
.endm
.macro clrHL	;reg
	clr	@0H
	clr	@0L
.endm
.macro outi	;io,imm (via R16!)
	ldi	r16,@1
	out	@0,r16
.endm
.macro subiHL	;reg,imm
	subi	@0L,LOW(@1)
	sbci	@0H,HIGH(@1)
.endm
.macro cpiHL	;reg,imm (via R17!)
	cpi	@0L,LOW(@1)
	ldi	r17,HIGH(@1)
	cpc	@0H,r17
.endm
.list

/*********************
 * Registerzuordnung *
 *********************/
// Dieses Programm benötigt keinen RAM, es sind noch 7 Register frei
.def prevL	=r0	// vorhergehender Zeitstempel in Pegelwechsel-ISR
.def prevH	=r1
.def periode	=r2	// Periodenzähler (exakte Frequenzmessung), 0..16, *
.def t0h	=r3	// „High-Teil“ von Timer0, Überlauf alle 0,5 s
.def sregsave	=r4	// SREG-Speicher für ISRs mit gesperrten Interrupts
.def riseL	=r8	// Timerwert bei steigender Flanke, *
.def riseH	=r9
.def L1phaL	=r10	// Zeitwert für 0°
.def L1phaH	=r11
.def tNL	=r12	// Periodendauer für N Perioden sowie Akkumulator
.def tNH	=r13
#if LDN>5
.def tNE	=r14
.def ZERO	=r15
#endif
// frei für ISR: R16 (T0OVL mit freien Interrupts), R17 (übrige Interrupts)
.def tprevL	=r18	// vorherige Periodendauer (Frequenz stabil?)
.def tprevH	=r19
.def state	=r20	// Synchronisierungs-Zustand
// Bit 0 = gültiges Signal (messbare Frequenz) anliegend — kein Blinken,
//	   dieser Zustand hält TOT*2 ms
// Bit 1 = synchronisiert — dieser Zustand hält TO Sekunden
//	   und verlängert sich mit vorhandenem plausiblen Signal
// Zustandstabelle für Bits 1:0
// 0 0	Kein Signal		Kein Synchrontimer	Blinken (3 LEDs)
// 0 1	Signal (neu)		Kein Synchrontimer	Frequenzmessung
// 1 1	Signal			Synchrontimer läuft	Phasenmessung
// 1 0	Kein Signal (mehr)	Synchrontimer läuft	Blinken (letzter Phasenwert)
// Bit 2 = Timer-ISR wollte L1pha inkrementieren (DPC)
// Bit 3 = AN ist HIGH; Timer-ISR darf L1pha nicht inkrementieren
// Bit 4 = DPC-Aufruf (aufgeschobener Prozeduraufruf) zur Phasenberechnung anhängig
// Bit 5 = Timer-Überlauf-ISR in Abarbeitung (Phasenberechnung wird aufgeschoben)
// Bit 6 = frei
// Bit 7 = Blink-Leuchtphase (sonst Pausenphase, kein PWM-Interrupt)
.def phase	=r21	// Phasenwinkel, 0..191
.def to		=r22	// TimeOut-Zähler (in 2ms oder ½s)
.def ledL	=r24	// Leuchtende LED für 0 .. OCR0B
.def ledH	=r25	// Leuchtende LED für OCR0B .. 0
.undef XH
.undef XL
.undef YH
.undef YL
.undef ZH
.undef ZL
.def curL	=r26	// Zeitstempel oder Periodendauer in Pegelwechsel-ISR
.def curH	=r27
.def lastL	=r28	// Glitch-Erkennung
.def lastH	=r29
			// * temporär, auch "missbräuchlich"

.org 0
	rjmp	startup		// * Reset
	.dw	(((DATEYEAR)-1980)<<9|(DATEMONTH)<5|(DATEDAY))	// INT0
	rjmp	pinchange	// * Pegelwechsel
	sbr	state,1<<5	// * (5) Timer0-Überlauf (Interrupts umgehend frei …)  
	sei			//   (1) EEPROM fertig   (… für genaue Zeitmessung …)
	inc	t0h		//   (1) Analogvergleicher  (… ohne Capture-Register)
	rjmp	t0ovl		//   Timer0-CompareA (Software-PWM)
	out	PORTB,ledH	// * Timer0-CompareB
	reti			//   Watchdog-Interrupt

/******************************
 * PWM und TimeOuts verwalten *
 ******************************/
// Diese ISR braucht SREG nicht zu retten, das Hauptprogramm benötigt's nicht.
// Interrupts sind hier freigeschaltet, damit der Pegelwechsel-Interrupt
// unverzüglich den Timer-Wert einfangen kann.
// VR: R16, SREG
t0ovl:	sbrc	state,7		// Blink-Leuchtphase?
	 out	PORTB,ledL	// ja, Phase 0 .. OCR0B
	brne	toe		// t0h == 0?
// Aller 0,5 s: (1) Phase nachführen, (2) Timeouts verwalten, (3) Blinken
	sbrc	state,1		// Synchronisiert?
	 rjmp	to2		// ja, andere TimeOut-Regeln
	sbrc	state,0		// Frequenz liegt an?
	 rjmp	toe		// ja, kurzes TimeOut auswerten
// 00► TAPO-TimeOut auswerten
	dec	to
	brne	tobl
// 00► Stromverbrauch runterfahren und warten auf Pegelwechsel-Interrupt
	outi	PORTB,0
	outi	MCUCR,0x70	// PowerDown (keine Takte)
	reti
// 1x► (1) Phase nachführen
to2:	sbrs	state,3		// Erlaubt (AN=Low?) — Bit nur einmal prüfen!
	 rjmp	to2a
	sbr	state,1<<2	// nein, DPC vermerken
	rjmp	to2b
to2a:	rcall	Inc_L1pha
// 1x► (2) TO-Timeout prüfen?
to2b:	sbrc	state,0		// Frequenz liegt an?
	 rjmp	toe		// ja, nur das kurze TimeOut auswerten
// 10► (2) TO-Timeout prüfen!
	dec	to		// InSync-TimeOut?
	brne	tobl		// nein, Synchronisation behalten und weiterblinken
	cbr	state,1<<1	// keine Synchronisation mehr!
	ldi	ledL,1<<L1|1<<L2|1<<L3
	ldi	ledH,1<<L1|1<<L2|1<<L3	// alle 3 LEDs blinken
	ldi	to,TAPO*2	// Auto-Power-Off-Timeout laden
tobl:
// x0► (3) Blinken: Blinkphase wechseln
	subi	state,0x80
	brmi	to5
	// Pausenphase beginnt
	outi	TIMSK0,0x02	// PWM aus ("outi TIFR0,8" kann entfallen!)
	outi	PORTB,0		// LEDs aus (Piepser ist sowieso schon aus)
	rjmp	toe
to5:	// Leuchtphase beginnt
	outi	TIMSK0,0x0A	// PWM ein (LEDs kommen umgehend)
toe:	// Mit jedem Timer-Interrupt (≈ 580 Hz) …
	sbrc	state,0		// Frequenz anliegend?
	 rcall	chktot		// ja, schnelles Time-Out mitzählen
	cli
	cbr	state,1<<5	// (1)
	sbrs	state,4		// (1)
	 reti			// (4)
	rcall	phasedpc
	cbr	state,1<<4
	reti

/* Wenn sich <phase> ändert, PWM neu einrichten
╔═══════╦═══════╤═══════╤═══════╗
║phase	║LedL	│LedH	│OCR0B	║
╟───────╫───────┼───────┼───────╢
║0	║1	│1	│0	║
║1..63	║2	│1	│4..252	║
║64	║2	│2	│0	║
║65..127║3	│2	│4..252	║
║128	║3	│3	│0	║
║129.191║1	│3	│4..252	║
╚═══════╩═══════╧═══════╧═══════╝
Leuchtintensität # = LED1, * = LED2, LED3 nicht dargestellt:
         ###      ***               ###      ***
      ###   ###***   ***         ###   ###***
   ###      ***###      ***   ###      ***###
###*********      ############*********      ###
-120      0       120      240      360      480°
*/
// VR: R17
PhaseChanged:
	// OCR0B mit Phase laden
	mov	r17,phase	// Doppelpufferung ist in Aktion
	lsl	r17
	lsl	r17
	out	OCR0B,r17
	ldi	ledH,1<<L1|1<<DO
	tst	phase
	breq	pc0
	ldi	ledL,1<<L2|1<<DO
	cpi	phase,64
	brcs	pce
	ldi	ledH,1<<L2|1<<DO
	breq	pce
	ldi	ledL,1<<L3|1<<DO
	cpi	phase,128
	brcs	pce
	ldi	ledH,1<<L3|1<<DO
	breq	pce
pc0:	ldi	ledL,1<<L1|1<<DO
pce:	ret

// x1► TimeOut alle 2 ms prüfen - ggf. zum Blinkbetrieb wechseln
chktot:	dec	to
	brne	tote
// x1► keine Frequenz mehr anliegend: blinken lassen
	cbr	state,1<<0|1<<7		// LED aus (Pausenphase)
	cbr	ledL,1<<DO	// Piepser künftig AUS
	cbr	ledH,1<<DO
	outi	TIMSK0,0x02	// Pausenphase, kein PWM mehr
	outi	PORTB,0		// Piepser und LEDs sofort AUS
	ldi	to,TO*2
	sbrs	state,1		// noch synchronisiert?
	 ldi	to,TAPO*2
tote:	ret

/*****************************
 * Frequenz und Phase messen *
 *****************************/

MulDiv:
// PE: cur = Dividend (vzl.)
//     rise = Divisor (vzl.), zwingend > Dividend
// PA: periode = Quotient (0..191) = Dividend*192/Divisor
//     cur = Rest
// VR: periode, tprev, rise, R17
l0:	clr	periode
	clr	r17
	movHL	tprev,cur	// cur:periode = 24-bit-Dividend (hier 256 × Dividend)
	lsrHL	tprev
	ror	r17
	lsrHL	tprev
	ror	r17		// tmp:R17 = 64 × Dividend
	sub	periode,r17
	sbc	curL,tprevL
	sbc	curH,tprevH	// cur:periode = 192 × Dividend
	ldi	r17,8		// Rundenzähler
l1:	lsl	periode	
	rol	curL
	rol	curH
	brcs	l2
	cmpHL	cur,rise
	brcs	l3
l2:	subHL	cur,rise
	inc	periode
l3:	dec	r17
	brne	l1
	ret

CalcPhase:
// PE: cur = Zeitstempel der letzten Impulsmitte (nach N Perioden)
// PA:
// VR: cur, rise, periode, R17
	subHL	cur,L1pha	// Zeitdifferenz
	brcs	cp1
cp0:	subHL	cur,tN	// auf ganze N Perioden normalisieren
	brcc	cp0	// max. 2 Runden
cp1:	addHL	cur,tN	// bei Unterlauf Periodendauer addieren (positiv machen)
	movHL	rise,tN
	ldi	r17,4
cp4:	lsrHL	rise	// LDN Wiederholungen des Schiebens
	dec	r17
	brne	cp4	// rise = Periodendauer _einer_ Periode
	adc	riseL,r17
	adc	riseH,r17	// runden
cp2:	subHL	cur,rise	// auf ganze Perioden normalisieren
	brcc	cp2	// max. 16 Runden
	addHL	cur,rise	// nunmehr 0 ≤ cur < rise
	rcall	MulDiv
	mov	phase,periode
	rjmp	PhaseChanged
	
// ISR für Pegelwechsel (auch asynchron zum Aufwecken möglich)
// Interrupts sind gesperrt
// VR: R17..R19 (neben Arbeitsregistern)
pinchange:
// Zuallererst 16-Bit-TickCount lesen, Interrupts müssen gesperrt sein
// curH:curL = Timer-Wert (überlauf-sicher)
// ► Capture nachbilden
	in	curL,TCNT0	// sofort Timerwert holen
	mov	curH,t0h
	in	r17,TIFR0
	sbrc	r17,1
	 ldi	curL,0xFF	// könnte übergelaufen sein (auf 0), zurückrunden
// ► Tiefschlaf beenden — Oszillator muss Timer antreiben
	ldi	r17,0x20
	out	MCUCR,r17	// zurück zum Schlafmodus mit Timer
// ► Glitch erkennen, nichts tun bei Glitch
	subHL	last,cur	// last = -Zeitdifferenz
	cpiHL	last,-TGLI
	brcc	pex		// Zeitdifferenz zu kurz!
	movHL	last,cur
	sbis	PINB,AN		// Steigende Flanke?
	 rjmp	ch1		// nein
// ► Steigende Flanke abspeichern
	sbr	state,1<<3
	movHL	rise,cur	// Nur Timerwert aufnehmen
pex:	reti

ch1:	in	sregsave,SREG
	cbr	state,1<<3
// ► Bei fallender Flanke H-Pulsmitte berechnen
	cmpHL	cur,rise	// im Regelfall C=0, bei 0-Überschneidung C=1
	in	r17,SREG	// C-Flag (Bit 0) retten
	addHL	cur,rise
	rorHL	cur		// 17-bit-Zwischenrergebnis benutzen, >>1
	sbrc	r17,0		// C (vom Vergleich) war gesetzt?
	 subi	curH,0x80	// MSB kippen!
// ► Periodendauer ermitteln
	subHL	cur,prev	// Zeitdifferenz zwischen 2 Pulsmitten
	addHL	prev,cur	// jetzigen Zeitstempel ablegen
// ► Periodendauer grob prüfen, raus bei Fehler (plötzlicher Phasensprung)
	cpiHL	cur,TMAX
	brcc	che		// T zu groß,  f < 45 Hz
	cpiHL	cur,TMIN
	brcs	che		// T zu klein, f > 65 Hz
// ► Gehe in Zustand „Frequenz anliegend“
	sbrc	state,0
	 rjmp	af
	sbr	state,1<<0|1<<7	// beende (langsames) Blinken
// x1► Bereite Periodendauermessung vor
	sbrc	state,1		// War synchronisiert?
	 rjmp	as
	clr	periode		// Frequenzmessung initialisieren
	clrHL	tN
// x1► Piepser aktivieren
as:	sbr	ledL,1<<DO
	sbr	ledH,1<<DO
	outi	TIMSK0,0x0A	// PWM ein (falls nicht bereits gesetzt)
// x1► Wenn nicht synchronisiert, Periodendauer bestimmen
af:	sbrc	state,1
	 rjmp	at
// 01► Periodendauer-Schwankung prüfen
	tst	periode		// Schon mal Periodendauer gemessen?
	breq	ch3		// nein, nur Vergleichswert abspeichern
	subHL	tprev,cur	// Differenz zum vorherigen Messwert
	subiHL	tprev,-TDEV
	subiHL	tprev,2*TDEV
	brcs	ch3		// T schwankt - Abweichung klein genug
// 01► Periodendauer-Messung neu starten
	clr	periode
	clrHL	tN
	rjmp	che
ch3:	movHL	tprev,cur	// zum nächsten Vergleich abspeichern
// 01► gültige Periodendauermessung, akkumulieren auf N Perioden (0,3 s)
	addHL	tN,cur		// Periodendauer aufsummieren (max. 36912 .. 60944)
#if LDN>5
	adc	tNE,ZERO
#endif
	inc	periode
// 01► nach N Perioden Frequenz- und Phasenauswertung
	sbrs	periode,LDN
	 rjmp	che
// x1► Phasenmessung initialisieren (x=0) oder Phase ausspucken (x=1)
at:	sbrs	state,5
	 rcall	phasedpc	// SetupPhase() oder ShowPhase()
	sbrc	state,5
	 sbr	state,1<<4
// xx► Schnelles TimeOut zur Detektierung des Pulsausfalls scharf machen
che:	sbrc	state,0
	 ldi	to,HIGH(TTOT)	// nur bei Zustand "x1"
	sbrc	state,2
	 rcall	Inc_L1pha
	out	SREG,sregsave
	reti
	
Inc_L1pha:
to22:	addHL	L1pha,tN	// addieren bis zum Überlauf
	brcc	to22		// Es bleibt 0 ≤ L1pha < tN
	cbr	state,1<<2
	ret

// (Möglicherweise) verzögerter, aufgeschobener Prozeduraufruf
// Interrupts müssen ausgeschaltet sein!
// PE: tNa = akkumulierte Periodendauer (N = 16 Perioden)
//     prev = Zeitstempel der letzten Impulsmitte
// PA: tN = Periodendauer für N (16) Perioden
//     L1pha = Zeitpunkt für 0°-Phase
//     phase = Phasenwert, 0..191
//     tNa = periode = 0
// VR: R17
phasedpc:
	movHL	cur,prev	// cur = Zeitstempel
	sbrs	state,1		// schon mal synchronisiert?
	 rjmp	SetupPhase
// 11► ShowPhase
	sbrc	curH,7
	 rjmp	CalcPhase
	sbrc	state,2
	 rcall	Inc_L1pha // Bei kleinem Zeitstempel L1pha für die neue Periode setzen
	rjmp	CalcPhase
// 01► SetupPhase: 0° annehmen
SetupPhase:
	sbr	state,1<<1	// Synchronisierung setzen
#if LDN == 5
	sec
	rorHL	tN		// halbieren und Bit 15 setzen
#endif
#if LDN>5
	ldi	phase,LDN-4
sp:	lsr	tNE
	rorHL	tN
	dec	phase
	brne	sp
#endif
ch5:	subHL	cur,tN		// auf kleinstmöglichen Wert normalisieren
	brcc	ch5		// maximal 2 Runden
	addHL	cur,tN		// nun 0 <= cur < tN
	movHL	L1pha,cur	// abspeichern
	clr	phase		// 0° liefern
	rjmp	PhaseChanged	// LEDs aktualisieren
	
/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/
startup:
	outi	SPL,RAMEND
	sbi	ACSR,ACD	// Analogvergleicher ausschalten
	outi	TCCR0A,0x03	// Schneller PWM-Modus ohne Portpins
	outi	TCCR0B,0x02	// Vorteiler 8; Überlauffrequenz ca. 500 Hz
	outi	DDRB,1<<L1|1<<L2|1<<L3|1<<DO	// LEDs als Ausgang
	outi	PCMSK,1<<AN
	outi	TIMSK0,0x0A	// 2 Timer0-Interrupts aktivieren
	outi	MCUCR,0x20	// Sleep (Idle-Modus) aktivieren
//	ldi	r16,0x20
	out	GIMSK,r16	// Pegelwechsel-Interrupt aktivieren
	ldi	state,0x80
	ldi	to,TAPO*2
	ldi	ledL,1<<L1|1<<L2|1<<L3
	ldi	ledH,1<<L1|1<<L2|1<<L3	// alle 3 LEDs blinken
#if LDN>5
	clr	ZERO
#endif
	sei
m1:	sleep
	rjmp	m1
Detected encoding: UTF-80